---
title: Grouping
---

Orama supports `groupBy` operations.
That allows you to group results in groups calculating an aggregation on the item that belongs to the same bucket.

```javascript
const results = await search(db, {
  term: "t-shirt",
  groupBy: {
    properties: ["design"], // required: property on which we want to group on
    maxResult: 1, // optional: for every group, how many results we want
    reduce: {
      // optional: customize the aggregation logic
      reducer: Function,
      getInitialValue: Function,
    },
  },
});
```

By default, Orama doesn't limit the number of items inside a group.

By default, Orama groups all the matched documents into an array.

## Simple usage

If we consider the following schema:

```javascript copy+
const db = await create({
  schema: {
    id: "string",
    type: "string",
    design: "string",
    color: "string",
    rank: "number",
    isPromoted: "boolean",
  },
});
const ids = await insertMultiple(db, [
  {
    id: "0",
    type: "t-shirt",
    design: "A",
    color: "blue",
    rank: 3,
    isPromoted: true,
  },
  {
    id: "1",
    type: "t-shirt",
    design: "A",
    color: "green",
    rank: 5,
    isPromoted: false,
  },
  {
    id: "2",
    type: "t-shirt",
    design: "A",
    color: "red",
    rank: 4,
    isPromoted: false,
  },
  {
    id: "3",
    type: "t-shirt",
    design: "B",
    color: "blue",
    rank: 4,
    isPromoted: false,
  },
  {
    id: "4",
    type: "t-shirt",
    design: "B",
    color: "green",
    rank: 4,
    isPromoted: true,
  },
  {
    id: "5",
    type: "t-shirt",
    design: "B",
    color: "white",
    rank: 5,
    isPromoted: false,
  },
  {
    id: "6",
    type: "t-shirt",
    design: "B",
    color: "gray",
    rank: 5,
    isPromoted: true,
  },
  {
    id: "7",
    type: "sweatshirt",
    design: "A",
    color: "yellow",
    rank: 3,
    isPromoted: true,
  },
  {
    id: "8",
    type: "sweatshirt",
    design: "A",
    color: "green",
    rank: 4,
    isPromoted: false,
  },
]);
```

We will be able to have the documents per `design` ordered by `rank`:

```javascript copy
const results = await search(db, {
  term: "t-shirt",
  groupBy: {
    properties: ["design"], // property on which we want to group on
  },
  sortBy: {
    property: "rank", // inside a group, the result is ordered following this property
    order: "DESC", // with this order
  },
});
```

If you want only the top-ranked document per `design`, you can specify the `maxResult`:

```javascript
const results = await search(db, {
  term: "t-shirt",
  groupBy: {
    properties: ["design"],
    maxResult: 1, // for every group, how many results we want
  },
  sortBy: {
    property: "rank",
    order: "DESC",
  },
});
```

The above query returns something like this:

```js
{
  groups: [
    {
      values: ['A'], // list of the values the group is referring to
      result: [
        {
          id: '1',
          score: 0,
          document: { ... } // the doc with id '1'
        }
      ]
    },
    {
      values: ['B'], // list of the values the group is referring to
      result: [
        {
          id: '5',
          score: 0,
          document: { ... } // the doc with id '5'
        }
      ]
    }
  ],
  // The other common properties like `hits` and `elapsed`
}
```

You can group on multiple properties as follows:

```javascript
const results = await search(db, {
  term: "red t-shirt",
  groupBy: {
    properties: ["design", "rank", "isPromoted"], // group on the combination of the values
  },
  sortBy: {
    property: "id",
    order: "ASC",
  },
});
```

## Custom reducer

Orama supports custom aggregator as follows:

```typescript
// The document interface
interface Doc extends Document {
  type: string;
  design: string;
  rank: number;
  color: string;
  isPromoted: boolean;
}
// The aggregation interface
interface AggregationValue {
  type: string;
  design: string;
  colors: string[];
  ranks: number[];
  isPromoted: boolean;
}

const results = await search(db, {
  term: "red t-shirt",
  groupBy: {
    properties: ["type", "design"], // group on both properties
    reduce: {
      // the accumulator function
      reducer: (
        values: ScalarSearchableValue[],
        acc: AggregationValue,
        item: Result
      ) => {
        const doc = item.document as Doc;
        acc.type ||= doc.type;
        acc.design ||= doc.design;
        acc.isPromoted ||= doc.isPromoted;
        acc.colors.push(doc.color);
        acc.ranks.push(doc.rank);
        return acc;
      },
      // The initial value: this is called for every group
      getInitialValue: (): AggregationValue => ({
        type: "",
        design: "",
        colors: [],
        ranks: [],
        isPromoted: false,
      }),
    },
  },
  sortBy: {
    property: "rank",
    order: "DESC",
  },
});
```

Where the accumulator function receives the following parameters:

1. the value of the current groups
2. the accumulator returned by the previous invocation
3. the item to accumulate

The reducer is called for every item for every group.
